#pragma once

#include <stx/core/shell.hpp>

inline namespace stx
{
enum class HttpMethod
{
    K_GET,
    K_POST
};

DEFINE_SAFE_NUMBER(HttpStatus, Status, 200);

using HttpHeaderDict = Map<std::string, std::string, CiLess>;

struct HttpRequestLine final
{
    HttpMethod  method = {};
    std::string uri;
};

struct HttpStatusLine final
{
    Status      status = {};
    std::string reason;
};

struct HttpRequest final
{
    HttpRequestLine req_line;
    HttpHeaderDict  header_dict;
    Buffer          body;
};

struct HttpResponse final
{
    HttpStatusLine status_line;
    HttpHeaderDict header_dict;
    Buffer         body;
};

struct HttpMsg final
{
    std::string    first_line;
    HttpHeaderDict header_dict;
    Buffer         body;
};

[[nodiscard]] std::string tos(HttpHeaderDict const& hdr_dict);
[[nodiscard]] std::string tos(HttpRequestLine const& req_line);
[[nodiscard]] std::string tos(HttpStatusLine const& status_line);

enum class HttpBodyDumpOption
{
    K_SKIP,
    K_AS_TEXT,
    K_AS_OCTETS,
};

[[nodiscard]] std::string
tos(HttpRequest const&       req,
    HttpBodyDumpOption const opt = HttpBodyDumpOption::K_SKIP);
[[nodiscard]] std::string
tos(HttpResponse const&      rsp,
    HttpBodyDumpOption const opt = HttpBodyDumpOption::K_SKIP);
[[nodiscard]] std::string
tos(HttpMsg const&           http_msg,
    HttpBodyDumpOption const opt = HttpBodyDumpOption::K_SKIP);

class HttpMsgParser final
{
    std::any imp_;

public:
    HttpMsgParser();

    [[nodiscard]] FsmState parse(char const c);
    [[nodiscard]] FsmState parse(MemView const mv);
    [[nodiscard]] HttpMsg  fetch();
    [[nodiscard]] FsmState getFsmState() const;
};

[[nodiscard]] std::optional<HttpRequestLine>
parseHttpRequestLine(std::string_view const sv_http_request_line);
[[nodiscard]] std::optional<HttpStatusLine>
parseHttpStatusLine(std::string_view const sv_http_status_line);

DEFINE_SAFE_NUMBER(MaxHttpMsgSize, std::size_t, 16_Mi);

// If HttpMsg::first_line begins with "err_msg:", it means a system error has
// occurred.
[[nodiscard]] Future<HttpMsg> recvHttpMsg(TcpSocket&           tcp_skt,
                                          Duration const       dur_timeout,
                                          MaxHttpMsgSize const max_msg_size);

[[nodiscard]] Future<std::ssize_t> sendHttpMsg(TcpSocket&        tcp_skt,
                                               std::string const s_first_line,
                                               HttpHeaderDict    hdr_dict,
                                               MemView const     mv_body,
                                               Duration const    dur_timeout);

[[nodiscard]] Future<HttpResponse>
sendHttpRequest(TcpSocket&             tcp_skt,
                HttpMethod const       method,
                std::string_view const sv_http_path,
                HttpHeaderDict         hdr_dict,
                MemView const          mv_body,
                Duration const         dur_timeout,
                MaxHttpMsgSize const   max_msg_size);

[[nodiscard]] Future<HttpResponse>
sendHttpRequest(std::string_view const sv_url,
                HttpMethod const       method,
                HttpHeaderDict         hdr_dict,
                MemView const          mv_body,
                Duration const         dur_timeout,
                MaxHttpMsgSize const   max_msg_size);

[[nodiscard]] Future<std::ssize_t>
sendHttpResponse(TcpSocket&             tcp_skt,
                 HttpStatus const       status,
                 std::string_view const sv_reason,
                 HttpHeaderDict         hdr_dict,
                 MemView const          mv_body,
                 Duration const         dur_timeout);

using FnOnRecvHttpMsg = std::function<Future<FsmState>(TcpSocket&,
                                                       HttpMsg,
                                                       RemoteEndpoint const&,
                                                       LocalEndpoint const&)>;

Future<> startHttpServer(LocalEndpoint        local_ep,
                         FnOnRecvHttpMsg      fn_on_recv_http_msg,
                         TcpBacklogSize const backlog_size,
                         SbReusePort const    sb_reuse_port,
                         Duration const       dur_timeout,
                         MaxHttpMsgSize const max_msg_size);

Future<> startHttpServer(LocalEndpoint   local_ep,
                         FnOnRecvHttpMsg fn_on_recv_http_msg);

/*
1. Query current state:

    curl http://127.0.0.1:60000/

2. Set LogLevel:

    curl http://127.0.0.1:60000/set/log-level/0
    curl http://127.0.0.1:60000/set/log-level/1

3. Enable TeeLog

    curl http://127.0.0.1:60000/enable/tee-log
	
4. Disable TeeLog

    curl http://127.0.0.1:60000/disable/tee-log
	
5. Enable Logger

    curl http://127.0.0.1:60000/enable/logger
	
6. Disable Logger

    curl http://127.0.0.1:60000/disable/logger
*/
Future<> startOobServer(Ip4 const ip4, FnOnRecvHttpMsg fn_on_recv_http_msg);
Future<> startOobServer(Ip4 const ip4);
Future<> startOobServer();

struct CurlResult final
{
    Status             status = 0;
    std::deque<Buffer> cerr;
    HttpResponse       rsp;
};

[[nodiscard]] std::string
tos(CurlResult const&        cr,
    HttpBodyDumpOption const opt = HttpBodyDumpOption::K_SKIP);

[[nodiscard]] Future<CurlResult> curl(std::string_view const sv_url,
                                      Duration const         dur_timeout,
                                      HttpHeaderDict const&  hdrs,
                                      MemView const          mv_req_body);
} // namespace stx

/*
url
---
- "my.com:7788"
- "1.2.3.4:7788"
- "my.com"
- "1.2.3.4"

http_path
---------
- "/profile"
- "/books/id/12345"

The Host HTTP header is mandatory since HTTP/1.1 and it's used for virtual 
hosting. It must include the domain name of the server, and the TCP port number
on which the server is listening. The port number may be omitted if the port is 
the standard port for the service requested (80 for HTTP and 443 for HTTPS).

startHttpServer(Endpoint("127.0.0.1"_ip4, Port(8080)),
    [](TcpSocket& tcp_skt, HttpMsg http_msg, RemoteEndpoint const&,
        LocalEndpoint const&) -> Future<FsmState>
    {
        co_await sendHttpResponse(
            tcp_skt, HttpStatus(200), "Good"sv, {}, "I love ypu!\n"sv, 5s);
        co_return FsmState::K_SUCCESS;
    });                
*/
